import random
import time,os,io
import traceback
from collections import deque
from socket import socket
from typing import List, Callable

import eventlet

from .proto.utils import read_no_wait, itob, zfill, read, btoi


class MultiDict:
    _keys: deque = list()
    _values: deque = list()

    def __getitem__(self, item):
        try:
            return self._values[self._keys.index(item)]
        except Exception as e:
            raise KeyError("%s not exists" % item)

    def __setitem__(self, key: str, value: 'Message'):

        if key in self._keys:
            self._values[self._keys.index(key)].append(value)
        else:
            self._values.append(value)
            self._keys.append(key)

    def __delitem__(self, key):
        try:
            index = self._keys.index(key)
            self._keys.pop(index)
            self._values.pop(index)
        except Exception as e:
            pass

    def keys(self):
        return self._keys

    def items(self):
        return zip(self._keys, self._values)

    def values(self):
        return self._values

    def pop(self, key):
        if key in self._keys:
            index = self._keys.index(key)
            msg: 'Message' = self._values[index]
            if msg.complete:
                self._keys.pop(index)
                return self._values.pop(index)


# 未来优化改动
class Message:
    msgid: str
    conetnt: bytes
    msgtyp: bytes
    result: List[bytes] = []
    total: int = -1
    send_count: int = 1
    del_message: Callable = None
    send_currrent: int = 1
    _recv_chan: eventlet.Queue
    _conn: 'Connection' = None

    def _gen_sid(self):
        return zfill(itob(random.randint(10001, 99999)), 4, left=True)

    def __init__(self, content, msgid=None, conn=None):
        self.msgid = msgid if msgid else self._gen_sid()
        self.send_count = 1
        self.send_currrent = 1
        self.msgtyp = content[0]
        self.result = []
        self._recv_chan = eventlet.Queue(maxsize=1)
        self._conn = conn
        send_count = zfill(itob(self.send_count), 2, left=True)
        send_currrent = zfill(itob(self.send_currrent), 2, left=True)
        s = self.msgid + send_count + send_currrent + content
        length = itob(len(s))
        # 将长度数据补位成2个字节
        length = zfill(length, 2, left=True)
        sign = b'\xcc'
        self.conetnt = b'\x55\xaa' + length + s + sign + b'\xaa\x55'

    def reply(self, msg: "Message"):
        assert self._conn, BlockingIOError('msg is not sended')
        self._conn.send(msg)

    def wait(self, timeout=30):
        if not self.complete:

            try:
                self._recv_chan.get(block=True, timeout=timeout)
            except Exception as e:
                if self.del_message:
                    self.del_message(self.msgid)
                raise e
        return self.result

    @property
    def complete(self) -> bool:
        return len(self.result) == self.total

    @property
    def conn(self) -> socket:
        return self._conn

    @conn.setter
    def conn(self, conn: socket) -> None:
        self._conn = conn

    @conn.deleter
    def conn(self):
        self._conn = None

    def append(self, value: bytes):
        self.result.append(value)

    def task_back(self, ):
        self._recv_chan.put(True)

    def hex(self):

        return f"send: {self.conetnt.hex()}\nrecv: {b''.join(self.result).hex()}\nmsgid: {self.msgid.hex()}"


class Connection:
    _connection: socket = None
    _msg_cache: MultiDict = MultiDict()
    _ReadQueue: eventlet.Queue = None
    _SendQueue: eventlet.Queue = None
    closed: bool = False
    _handle_type: list = [
        b'\x07',
        b'\x09',
        b'\x0c',
        b'\x13',
    ]


    def __init__(self, conn: socket):
       
        self._connection = conn
        self._msg_cache = MultiDict()
        self.closed = False
        self._ReadQueue = eventlet.Queue(100)
        self._SendQueue = eventlet.Queue(100)

    def recv(self,) -> Message:
        return self._ReadQueue.get()


    def _send_loop(self):
        # sf:io.FileIO = os.fdopen(self._connection.fileno())
    
        while not self.closed:
            msg,save = self._SendQueue.get()
            print(msg.hex())
            if not save:
                self._connection.sendall(msg.conetnt)
  
                continue
            while msg.msgid in self._msg_cache.keys():
                print('msgid is repeat in cache, now rebuild msgid')
                msg.msgid = msg._gen_sid()
            self._connection.sendall(msg.conetnt)
            msg.conn = self._connection
            msg.del_message = self.del_message
            self._msg_cache[msg.msgid] = msg
            # sf.flush()
            time.sleep(0.5)
        # sf.close()
    def send(self, msg: Message,save=True):
            
         self._SendQueue.put((msg,save))
        
        

    def del_message(self, sid):
        print(f'{sid.hex()} is timeout ,deleteing from cache')
        self._msg_cache.pop(sid)

    def loop(self):
        eventlet.spawn_n(self._read_loop)
        eventlet.spawn_n(self._send_loop)


    def _read_loop(self):
        while not self.closed:
            eventlet.sleep(0.2)
            try:
                msg_type, data, msgid, total_package, current_package = read(
                    self._connection)
                
                if current_package != b'\x00\x01':
                    data = b'\x00' + data
                if msg_type in self._handle_type:
                    msg =Message(
                        content=msg_type+data,
                        msgid=msgid,
                        conn=self._connection
                    )
                    msg.content = data
                    self._ReadQueue.put(msg)
                elif msgid in self._msg_cache.keys():
                    self._msg_cache[msgid].append(data)
                    self._msg_cache[msgid].total = btoi(total_package)
                    if self._msg_cache[msgid].complete:
                        self._msg_cache[msgid].task_back()
                        self._msg_cache.pop(msgid)
                    eventlet.sleep()
            except BlockingIOError as e:
                pass
            except OSError as e:
                self.close()
                print('================')
                traceback.print_exc()
                return
            except Exception as e:
                traceback.print_exc()
                pass

    def close(self):
        self._connection.close()
        self.closed = True
